#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
This module is developed according to the concept of  JUnit testing framework, and refer 
to the implementation of Python's unittest module. And, it base on and extend the 
Monkeyrunner API from google, means to be a Android AutoTest Framework for Android device.

This module is a public domain software, and you may redistribute it and/or modify
it under the same terms as LGPL2.1 license itself, so long as this copyright message
and disclaimer are retained in their original form.

IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

© 2011 Sunny Lin. All Rights Reserved. 
Created on 2011/10/7
'''
__author__ = "Sunny Lin"
__email__ = "013.lin@gmail.com"
__version__ = "0.13"

import math, operator
from com.android.monkeyrunner import MonkeyDevice,MonkeyRunner,MonkeyImage
from aaf.circusUtility import *
from aaf.circusReporter import *
from aaf.monkeyErrors import *

##############################################################################
# Exported classes and functions
##############################################################################
__all__ = ['AndroidDevice', 'MonkeyTrick', 'MonkeyShow', 'MonkeyTamer', 'MonkeyError']

def getProjectCode(trickObj):
  if (isinstance(trickObj, MonkeyShow)):
    obj=trickObj.tricks[0] # retrieve testcase object
  else : obj=trickObj
  path=decode_ascii(obj.__class__.__module__)
  names=path.split('.')
  if (len(names) < 4) :
    g_logger.critical("TestCase object, %s, is not under project directory." % (trickObj.__class__.__name__))
    raise MonkeyError(("TestCase object, %s, is not under project directory." % (trickObj.__class__.__name__)))
  return names[2][1:] #sqa.monkeytest.pCode

def getTestCategory(trickObj):
  if (not isinstance(trickObj, MonkeyTrick)):
    g_logger.critical(("TestCase object, %s, is not a subclass of MonkeyTrick" % (trickObj.__class__.__name__)))
    raise MonkeyError(("TestCase object, %s, is not a subclass of MonkeyTrick" % (trickObj.__class__.__name__)))
  totalname=decode_ascii(trickObj.__class__.__module__)
  names=totalname.split('.')
  if (len(names) < 4) :
    g_logger.critical(("TestCase object, %s, is not under project directory." % (trickObj.__class__.__name__)))
    raise MonkeyError(("TestCase object, %s, is not under project directory." % (trickObj.__class__.__name__)))
  category=names[3]
  for i in range(4, len(names)) :
    category+=('.'+names[i])
  return category

def getTestcaseName(trickObj):
  if (not isinstance(trickObj, MonkeyTrick)):
    g_logger.critical(("TestCase object, %s, is not a subclass of MonkeyTrick" % (trickObj.__class__.__name__)))
    raise MonkeyError(("TestCase object, %s, is not a subclass of MonkeyTrick" % (trickObj.__class__.__name__)))
  name=decode_ascii(trickObj.__class__.__name__)
  return name
  

def getProjectPath(trickObj):
  if (isinstance(trickObj, MonkeyShow)):
    obj=trickObj.tricks[0] # retrieve testcase object
  else : obj=trickObj
  path=decode_ascii(obj.__class__.__module__)
  names=path.split('.')
  path=names[0]+os.sep+names[1]+os.sep+names[2]
  return os.path.abspath(path)

def getPackagePath(trickObj):
  path=decode_ascii(trickObj.__class__.__module__)
  names=path.split('.')
  modulename=names[len(names)-1]
  path=path.replace('.'+modulename,'')
  path=path.replace('.', os.sep)
  return os.path.abspath(path)

def getResourcePath(trickObj, scope=ConfigScopeEnum.PACKAGE):
  if (isinstance(trickObj, MonkeyShow)):
    obj=trickObj.tricks[0] # retrieve a testcase object
  else : obj=trickObj
  if (scope==ConfigScopeEnum.PROJECT) :
    path=getProjectPath(obj)+os.sep+"resources"
  else:
    path=getPackagePath(obj)+os.sep+"resources"
  return path

def getResultPath(trickObj, scope=ConfigScopeEnum.PACKAGE):
  if (isinstance(trickObj, MonkeyShow)):
    obj=trickObj.tricks[0] # retrieve testcase object
    run_id=trickObj.run_id
  else : 
    obj=trickObj
    run_id=trickObj.my_show.run_id
  if (scope==ConfigScopeEnum.PROJECT) :
    path=getProjectPath(obj)+os.sep+"results"+os.sep+str(run_id)
  else:
    path=getPackagePath(obj)+os.sep+"results"+os.sep+str(run_id)
  return os.path.abspath(path)

def getConfigFile(trickObj, scope=ConfigScopeEnum.PACKAGE):
  if ((scope==ConfigScopeEnum.CLASS) or (scope==ConfigScopeEnum.PACKAGE)) :
    path=getPackagePath(trickObj)+os.sep+"conf"
  elif (scope==ConfigScopeEnum.PROJECT) :
    path=getProjectPath(trickObj)+os.sep+"conf"
    
  if (scope==ConfigScopeEnum.CLASS) :
    filename=trickObj.__class__.__name__+".ini"
  elif (scope==ConfigScopeEnum.PACKAGE) :
    names=(trickObj.__class__.__module__).split('.')
    packagename=names[len(names)-2]
    filename=packagename+".ini"
  elif (scope==ConfigScopeEnum.PROJECT) :
    filename=getProjectCode(trickObj)+".ini"
    
  confFile=path+os.sep+filename
  return confFile
   
def loadActionMenu(obj, scopes=ConfigScopeEnum.allList()):
  ''' load actionArea define from resources, $modulename.ini '''  
  action_menu=ActionMenu()
  for scope in scopes :
    config = ConfigParser.SafeConfigParser()
    config.optionxform = str # make configParser to be case sensitivity
    confFile=getConfigFile(obj, scope) 
    if (not os.path.exists(confFile)) : 
      continue    
    try :
      config.read(confFile) #confFile
      actionarea_section = config.items("ActionArea")
    except :  
      print sys.exc_info()
      continue
    for item in actionarea_section:
      values=item[1].split(',')
      if (scope==ConfigScopeEnum.PROJECT) :
        area_name=getattr(AreaNameRegistry(), item[0]) # Project scope's area name is reflected  to that defined in AreaNameRegistry
      else :
        area_name=item[0]
      action_area=ActionArea((int(values[0]),int(values[1])), (int(values[2]),int(values[3])), area_name, AreaTypeEnum.string2Type(values[4]))
      action_menu.addActionArea(action_area)
    
    config = None
  return action_menu

def getTestStepNames(trickClass, testMethodPrefix):
    """Return a sorted sequence of method names, started with 'testStep' found within MonkeyTrick
    """    
    def isStepMethod(attrname, testCaseClass=trickClass, prefix=testMethodPrefix):
        return attrname.startswith(prefix) and callable(getattr(testCaseClass, attrname))
    testFnNames = filter(isStepMethod, dir(trickClass))
    for baseclass in trickClass.__bases__:
        for testFnName in getTestStepNames(baseclass, testMethodPrefix):
            if testFnName not in testFnNames:  # handle overridden methods
                testFnNames.append(testFnName)
    testFnNames.sort(cmp)
    return testFnNames


class DeviceBase:
  ''' Just as a base class for all of device '''
  pass

class AndroidDevice(DeviceBase) :
  def __init__(self, trick_owner, monkey_device, action_menu):
    self.trick_owner=trick_owner
    self.monkey_device=monkey_device
    self.action_menu=action_menu
    self.width=int(monkey_device.getProperty("display.width"))
    self.height=int(monkey_device.getProperty("display.height"))
    self.project_code=getProjectCode(trick_owner) #just get project code from one of testCase object
  
  def getProperty(self, property_name):
    '''
    To get the android's property, that is delegated to MonkeyDevice.getPRoperty. 
    And, the supported property name is listed as below
    property_names=("build.board","build.brand","build.device","build.fingerprint","build.host","build.ID","build.model",
                    "build.product","build.tags","build.type","build.user","build.CPU_ABI","build.manufacturer",
                   "build.version.incremental","build.version.release","build.version.sdk","build.version.codename",
                   "display.width","display.height","display.density",
                   "am.current.package","am.current.action","am.current.comp.class","am.current.comp.package","am.current.data","am.current.categories",
                   "clock.realtime","clock.uptime","clock.millis")
    '''    
    value=self.device.getProperty(property_name)
    value=unicode(value,'utf-8').encode('utf-8') # if no this transformation, will cause exception LookupError: unknown encoding 'ms950', reason still unknown
    return value
       
  def getArea(self, area_name):
    area=self.action_menu.getActionArea(area_name)
    return area   
  
  def isAreaDefined(self, area_name):
    return self.action_menu.hasActionArea(area_name)
  
  def takePicture(self, area=None, noError=False):
    ''' according to action_area, take a snap shot of phone screen
      if (noError) : not raise exception for any error, but return a total area's snapshot
    '''    
    pic = self.monkey_device.takeSnapshot()
    if (area is None) :
      pass
    elif (isinstance(area, Rect)):
      pic=pic.getSubImage(area.toTupleWH())
    elif (isinstance(area, tuple)):
      pic=pic.getSubImage(area)
    elif isinstance(area, basestring):
      area_new=self.action_menu.getActionArea(area)
      if area_new is None : 
        if(not noError) : 
          err=MonkeyAreaUndefinedError(area)
          g_logger.critical(err.message)
          raise err
      else : 
        pic=pic.getSubImage(area_new.toTupleWH())
    else :
      if(not noError) : 
        err=MonkeyUnsupportedParameterTypeError(type(area))
        g_logger.critical(err.message)
        raise err
    return pic
  
  def loadPicture(self, filename, scope=ConfigScopeEnum.PACKAGE):
    loadfile=getResourcePath(self.trick_owner, scope)+os.sep+filename+".png"
    if (not os.path.exists(loadfile)) : 
      err=MonkeyImageNotFoundError(loadfile)
      g_logger.critical(err) 
      raise err
      return None
    pic=None
    try :
      strat_time = time.time()
      pic = MonkeyRunner.loadImageFromFile(loadfile)
      stop_time = time.time()
      time_taken = stop_time - strat_time
      g_logger.debug("MonkeyRunner load in %5fs" %time_taken)
    except:
      err=MonkeyError(("Load image file:%s fail!") %loadfile)
      g_logger.critical(err.message)
      raise err 
    return pic

  def savePicture(self, pic, filename, scope=ConfigScopeEnum.PACKAGE):
    path=getResultPath(self.trick_owner, scope)
    if (not os.path.exists(path)) : os.makedirs(path)
    savefile=path+os.sep+filename+".png"
    pic.writeToFile(savefile,'png')

  def comparePicture(self, pic1, pic2, precise=1.0):
    '''
    '''
    comparedCount=0.0
    successCount=0.0
    failCount=0.0
    if (pic1 is None or pic2 is None) :
      #g_logger.debug("One of picture is null!!!")
      return False
    if (precise==1.0) :
      byte1=pic1.convertToBytes("png")
      byte2=pic2.convertToBytes("png")
      return (byte1==byte2)
    
    max_x=self.width
    max_y=self.height
    alpha_bit=int('11111111000000000000000000000000', 2)
    red_bit=  int('00000000111111110000000000000000', 2)
    green_bit=int('00000000000000001111111100000000', 2)
    blue_bit= int('00000000000000000000000011111111', 2)
    rgb_bit=(red_bit | green_bit | blue_bit)
    x=0 #test y size
    for y in range(0,max_y):
      try :
        pixel1=(pic1.getRawPixelInt(x,y))
        pixel2=(pic2.getRawPixelInt(x,y))
        alpha1=(pixel1 & alpha_bit)
        alpha2=(pixel2 & alpha_bit)
        pixel1=(pixel1& rgb_bit)
        pixel2=(pixel2& rgb_bit)
        comparedCount +=1
        if ((alpha1==0) or (alpha2==0) or (pixel1==pixel2)) :
          successCount+=1
        else :
          failCount+=1
      except : # ArrayIndexOutOfBoundsException
        break
    max_y=y
    y=0 #test x size
    for x in range(0,max_x):
      try :
        pixel1=(pic1.getRawPixelInt(x,y))
        pixel2=(pic2.getRawPixelInt(x,y))
        alpha1=(pixel1 & alpha_bit)
        alpha2=(pixel2 & alpha_bit)
        pixel1=(pixel1& rgb_bit)
        pixel2=(pixel2& rgb_bit)
        comparedCount +=1
        if ((alpha1==0) or (alpha2==0) or (pixel1==pixel2)) :
          successCount+=1
      except : # ArrayIndexOutOfBoundsException
        break
    max_x=x 
    for x in range(1,max_x):
      for y in range(1,max_y):
        try :
          pixel1=(pic1.getRawPixelInt(x,y))
          pixel2=(pic2.getRawPixelInt(x,y))
          alpha1=(pixel1 & alpha_bit)
          alpha2=(pixel2 & alpha_bit)
          pixel1=(pixel1& rgb_bit)
          pixel2=(pixel2& rgb_bit)
          comparedCount +=1
          if ((alpha1==0) or (alpha2==0) or (pixel1==pixel2)) :
            successCount+=1
        except : # Should not happen again. ArrayIndexOutOfBoundsException
          g_logger.debug(sys.exc_info())
          continue
      pass
    g_logger.debug("success=%d, compared=%d" % (successCount, comparedCount))
    passRate=successCount/comparedCount
    g_logger.debug("PassRate:%f, %f" % (passRate, precise))
    return (passRate >= precise)

  def isExistAreaPic(self, area_name, precise=1.0):
    ''' Test screen has icon in defined area, and that is the same as file in resource named by area_name '''
    if self.isAreaDefined(area_name) :
      pic=self.takePicture(area_name)
      ref1=self.loadPicture(area_name, ConfigScopeEnum.PROJECT) # load file with 'area_name' to comap
      result=self.comparePicture(pic, ref1, precise)
      return (result)#pic.sameAs(reference) 
    else :
      return False

  def touchArea(self, area_name, position=PositionEnum.CENTER, downuptype=MonkeyDevice.DOWN_AND_UP, times=1):
    action_item=self.action_menu.getActionArea(area_name)
    if action_item is None : 
      err=MonkeyAreaUndefinedError(area_name)
      g_logger.critical(err.message)
      raise err
    else :
      pt=action_item.getSubArea(position).center()
      self.monkey_device.touch(pt.x, pt.y, downuptype)

  def longTouchArea(self, area_name, position=PositionEnum.CENTER, duration=3):
    action_item=self.action_menu.getActionArea(area_name)
    if action_item is None : 
      err=MonkeyAreaUndefinedError(area_name)
      g_logger.critical(err.message)
      raise err
    else : 
      pt=action_item.getSubArea(position).center()
      self.monkey_device.touch(pt.x, pt.y, MonkeyDevice.DOWN)
      time.sleep(duration)
      self.monkey_device.touch(pt.x, pt.y, MonkeyDevice.UP)

  def dragArea(self, area_name, direction=DirectionEnum.TOP2BOTTOM, duration=1.0, steps=10):
    ''' Get two area according to Direction, then drag for the center of one Area to another. '''
    action_item=self.action_menu.getActionArea(area_name)
    if action_item is None : 
      err=MonkeyAreaUndefinedError(area_name)
      g_logger.critical(err.message)
      raise err
    else :
      areas=action_item.getDirectionAreas(direction)
      pt1=areas[0].center()
      pt2=areas[1].center()
      self.monkey_device.drag(pt1.toTuple(), pt2.toTuple(), duration, steps) 

  def dragFromTo(self, from_pt_area, to_pt_area, touch_duration=0, drag_duration=1.0, steps=10):
    ''' if ?_pt_area is an area name, then drag from one center of area to another 
        else ?_pt_area is a Point or tuple, then just drag from one Point(tuple) to another.
    '''
    if (isinstance(from_pt_area, Point)):
      from_pt=from_pt_area
    elif (isinstance(from_pt_area, tuple)):
      from_pt=Point(from_pt_area[0], from_pt_area[1])
    else :
      from_action_item=self.action_menu.getActionArea(from_pt_area)
      if from_action_item is None : 
        err=MonkeyAreaUndefinedError(from_action_item)
        g_logger.critical(err.message)
        raise err
      else :
        from_pt=from_action_item.getSubArea(PositionEnum.CENTER).center()
    if (isinstance(to_pt_area, Point)):
      to_pt=to_pt_area
    elif (isinstance(to_pt_area, tuple)):
      to_pt=Point(to_pt_area[0], to_pt_area[1])
    else :
      to_action_item=self.action_menu.getActionArea(to_pt_area)
      if to_action_item is None : 
        err=MonkeyAreaUndefinedError(to_action_item)
        g_logger.critical(err.message)
        raise err
      else :
        to_pt=to_action_item.getSubArea(PositionEnum.CENTER).center()        
    g_logger.debug("start dragFromTo: %s --> %s" % (from_pt, to_pt))
    if(touch_duration>0) :
      self.monkey_device.touch(from_pt.x, from_pt.y, MonkeyDevice.DOWN)
      time.sleep(touch_duration)
    self.monkey_device.drag(from_pt.toTuple(), to_pt.toTuple(), drag_duration, steps) 
    
  def pressArea(self, area_name, keycode, downuptype=MonkeyDevice.DOWN_AND_UP, need_focus=True):
    ''' Focus an Area, and then press a key, according to keycode '''
    action_item=self.action_menu.getActionArea(area_name)
    if action_item is None : 
      err=MonkeyAreaUndefinedError(area_name)
      g_logger.critical(err.message)
      raise err
    else :
      if need_focus : self.monkey_device.touch(action_item.x, action_item.y, MonkeyDevice.DOWN_AND_UP) # get focus first
      self.monkey_device.press(keycode, downuptype) 
     
  def typeArea(self, area_name, string, need_focus=True):
    ''' Focus an Area, and then type string into it '''
    action_item=self.action_menu.getActionArea(area_name)
    if action_item is None : MonkeyAreaUndefinedError(area_name)
    else :
      if need_focus : self.monkey_device.touch(action_item.x, action_item.y, MonkeyDevice.DOWN_AND_UP) # get focus first
      self.monkey_device.type(string) 
      
  def startApp(self, appName):
    self.monkey_device.startActivity(component=appName)
   
  def wake(self):
    self.monkey_device.wake()

  def reboot(self):
    ''' ToDo: Android phone's reboot function '''
    self.monkey_device.reboot()
    pass

  def contextMenu(self):
    ''' ToDo: Android phone's context menu function '''
    pass
  
  def home(self):
    ''' Android phone's home function, return from any application '''
    self.monkey_device.press("KEYCODE_HOME", MonkeyDevice.DOWN_AND_UP)
    
  def isHome(self):
    ''' ToDo : test if at home screen now '''
    pass
    
  def back(self):
    ''' ToDo:Android phone's back function '''
    pass
    
  def search(self):
    ''' ToDo : Android phone's search function '''
    pass
  def iconMenu(self):
    ''' ToDo:Android phone's icon menu function at home '''
    pass
  
  def call(self):
    ''' ToDo:Activate Android phone's call at home '''
    pass
  
  def browser(self):
    ''' ToDo:Activate Android phone's browser at home '''
    pass
 
  def nextPage(self):
    ''' ToDo:Android phone - slide left to next page at home '''
    pass
  
  def prevPage(self):
    ''' ToDo:Android phone - slide right to prev page at home '''
    pass

  def isLocked(self):
    ''' Test screen has lock icon '''
    return self.isExistAreaPic(AreaNameRegistry.LOCK, 0.4) 
  
  def unlock(self):
    '''  implement to unlock phone device by dragging "LOCK_AND_SOUNC" area '''
    self.dragArea(AreaNameRegistry.LOCK_AND_SOUND, DirectionEnum.LEFT2RIGHT)
#End AndroidDevice    
     
class MonkeyTrick: 
  """ The foundation class for all of testcase that use monkeyrunner to test android phone

      Inheritance this class, and implement method name start with 'testStep', that means the step will be
      executed in this test case class. 
      example refer to module calculatorTest  
  """
  testStepPrefix="testStep"
  def __init__(self, round_=1, monkey_device=None):
    ''' Always use 'run' as the test method of TestCase, and the default behavior 
        of 'run' is to run 'testStep#' one by one
        
        By default, a 'MonkeyShow' share the same device for all of 'MonkeyTrick', but you still
        can pass a specific device for each 'MonkeyTrick' to play
    '''
    if(monkey_device is not None) : 
      action_menu=loadActionMenu(self, ConfigScopeEnum.allList()) #[ConfigScopeEnum.PACKAGE, ConfigScopeEnum.CLASS]
      device=AndroidDevice(self, monkey_device, action_menu)
    else : device=None
    self.my_device=device
    self.round_=round_  #add tail '_' to prevent reserve key word's warning

  @property  
  def id(self):
    return self.__str__()
    
  @property
  def name(self):
    return getTestcaseName(self)

  @property
  def description(self):
    return self.__class__.__doc__
  
  def getStepDescription(self, step_name=None):
    '''  List the test step's description, if no step_name pass, the get all 
    '''
    if (step_name is None):
      testFnNames=getTestStepNames(self.__class__, self.testStepPrefix)
      i=1

    else :
      testFnNames=[step_name]
      i=int(step_name.replace('testStep', ''))
    steps_desc=""
    for stepName in testFnNames :
      testStep = getattr(self, stepName)
      if(len(steps_desc)>0) :
        steps_desc+="\n"
        steps_desc+=("%d.%s" % (i, testStep.__doc__))
      i=i+1
    return steps_desc

  @property
  def step_count(self):
    testFnNames=getTestStepNames(self.__class__, self.testStepPrefix)
    return (len(testFnNames))
    
  def __str__(self):
    projcode=getProjectCode(self)
    category=getTestCategory(self)
    testname=getTestcaseName(self)
    return "%s.%s.%s" % (projcode, category, testname)

  def __repr__(self):
    testname=getTestcaseName(self)
    return "%s:%s" % (testname, self.description)

  def setUp(self, reporter=None):
    ''' Hook method for setting up the 'MonkeyTrick' fixture before exercising it.'''
    self.my_device.wake()
    time.sleep(1)
    if self.my_device.isLocked() :
      #print "Unlock phone ...."
      self.my_device.unlock()   
   
  def tearDown(self, reporter=None):
    ''' Hook method for tearing down the 'MonkeyTrick' fixture after exercising it.
        Here we will save the last screen snapshot to ./result/$TestClassName_finalSnapShot
    '''
    if(reporter is not None and reporter.logger.getEffectiveLevel()==logging.DEBUG):
      filename=self.__class__.__name__+"_final"
      self.my_device.savePicture(self.my_device.takePicture(), filename)
 
  def play(self, reporter):
    ''' The only method to play monkey trick, and this will run the 'testStep' one by one '''
    def getTestStepFns():
      testFnNames=getTestStepNames(self.__class__, self.testStepPrefix)
      return testFnNames
    
    recorder=reporter.recorder
    logger=reporter.logger 
    if (self.my_device is None) :
      err=MonkeyError("No MonkeyDevice provided to play trick.")
      logger.critical(err.message)
      recorder.recordException(self, 0, "setUp", err)
    if(self.round_<1) :
      return
    recorder.start(self) 
    for round_ in range(1, self.round_+1) :
      logger.info(("Round (%d) ..." % (round_)))
      try:
        logger.debug(("setUp TestCase : %s " % (self.name)))
        self.setUp()
      except KeyboardInterrupt, e:
        recorder.recordException(self, round_, "setUp", e)
        logger.warning(e.message)
        raise
      except BaseException, e:
        recorder.recordException(self, round_, "setUp", e)
      time.sleep(1)
      testFnNames=getTestStepFns()
      for stepName in testFnNames :
        try :
          prestep=getattr(self, "preStep")
        except : pass
        else :
          logger.debug(("%s:%s" % ("preStep", prestep.__doc__)))
          prestep()
          
        try:
          testStep = getattr(self, stepName)
          logger.info(("%s:%s" % (stepName, testStep.__doc__)))
          self.__runStep(testStep, reporter)
        except MonkeyError, e:
          logger.critical(e.message)
          recorder.recordException(self, round_, stepName, e)         
        except MonkeyFailure, f:
          logger.error(f.message)
          recorder.recordException(self, round_, stepName, f)
        except KeyboardInterrupt, e:
          logger.warning(e.message)
          recorder.recordException(self, round_, stepName, e)
          raise
        except BaseException, e:
          logger.critical(e.message)
          recorder.recordException(self, round_, stepName, e)
        
        try :
          poststep=getattr(self, "postStep")
        except : pass
        else :
          logger.debug(("%s:%s" % ("postStep", prestep.__doc__)))
          poststep()
      # End of testStep loop
      try:
        logger.info(("tearDown TestCase %s" % (self.name)))
        self.tearDown()
      except KeyboardInterrupt, e:
        logger.warning(e.message)
        recorder.recordException(self, round_, "tearDown", e)
        raise
      except BaseException, e:
        logger.critical(e.message)
        recorder.recordException(self, round_, "tearDown", e)       
      # end of run each round   
    recorder.stop(self) 
        
  def _setMyShow(self, show):
    ''' Setup which show the trick belon to. This is just for setting the monkey_device and getting the show id '''
    if isinstance(show, MonkeyShow):
      self.my_show=show
      if (self.my_device is None) :
        action_menu=loadActionMenu(self, ConfigScopeEnum.allList()) #[ConfigScopeEnum.PACKAGE, ConfigScopeEnum.CLASS]
        self.my_device=AndroidDevice(self, show.monkey_device, action_menu)
    else :
      raise TypeError("The test case to add must be a instance of MonkeyTrick.") 
       
  def __runStep(self, testStep, reporter):
    testStep(reporter)
    checkstepname=("check%s" % testStep.__name__)
    try :
      checkstep=getattr(self, checkstepname)
    except : 
      time.sleep(1)
      self.__checkStep(testStep.__name__, reporter)
      time.sleep(1)
    else :
      
      checkstep()
    
  def __checkStep(self, stepname, reporter, precise=1.0):
    ''' this is a default implementation for every step to check. '''
    def __getStepResourceName():
      return (self.__class__.__name__+"_"+stepname)
    
    logger=reporter.logger 
    logger.info(("Checking step %s." %stepname))
    result_pic=self.my_device.takePicture(stepname, noError=True)
    try :
      check_pic=self.my_device.loadPicture(__getStepResourceName())
    except MonkeyError :
      check_pic=None
    result=None
    if ((check_pic is not None) and (result_pic is not None)):
      result=self.my_device.comparePicture(result_pic, check_pic, precise)
    if (result is None) :
      result_str="unknown"      
    elif (result):
      result_str="success"
    else :
      result_str="fail"
    pic_file=__getStepResourceName()+"_"+result_str
    logging_level=logger.getEffectiveLevel()
    if(logging_level==logging.DEBUG):
      self.my_device.savePicture(result_pic, pic_file)
      logger.info(("Step result saved as:%s" % (pic_file+".png")))
    if(result_str=="unknown"):
      logger.warning("%s is not checked, for no pre-stored picture to check result." % (stepname))
    if (result_str=="fail"):
      raise MonkeyFailure(result_pic, ("%s failed. Snapshot is saved:%s" % (stepname, pic_file+".png"))) 
# End MonkeyTrick

class MonkeyShow : 
  """ 
    A MonkeyShow is a composite test consisting of a instance of MonkeyTrick.
    For use, create an instance of MonkeyShow, then add any instance of subclass of MonkeyTrick.
    When all tests have been added, the suite can be passed to a test
    runner, such as MonkeyTamer. It will run the individual test cases
    in the order in which they were added, aggregating the results.
    Example:
    show=MonkeyShow((CalculatorPlusTrick(), CalculatorMultipleTrick()))
    MonkeyTamer().run(testshow)
  """ 
  def __init__(self, tricks=(), deviceId=None, round_=1):
    if(deviceId is None) : monkey_device=MonkeyRunner.waitForConnection()
    else : monkey_device=MonkeyRunner.waitForConnection(deviceId)
    self.monkey_device=monkey_device
    self.tricks=[]
    self.addTricks(tricks)
    t=time.localtime()
    self.run_id = ("%02d%02d%02d%02d%02d" % (t[1], t[2], t[3], t[4], t[5])) # mmddhhmmss
    self.round_=round_
    
  def __repr__(self):
    return "<%s tricks=%s>" % (self.__class__.__name__, self.tricks)

  __str__ = __repr__

  def __iter__(self):
    return iter(self.tricks)
  
  def count(self):
    return len(self.tricks)

  def addTricks(self, tricks):
    for trick in tricks:
        self.addTrick(trick)
        
  def addTrick(self, trick):
    if isinstance(trick, MonkeyTrick):
      trick._setMyShow(self)
      self.tricks.append(trick)
    else :
      raise TypeError("The test case to add must be a instance of MonkeyTrick.")

  def play(self, reporter):
    ''' The original method to run TestSuite, and we delegate to startShow. '''
    logger=reporter.logger
    for i in range(0, self.round_) :
      for trick in self.tricks:
        logger.info(("=== %s show Start ===" %trick.name))
        trick.play(reporter)
        logger.info(("=== %s show End. ===" %trick.name))
    return reporter
# End MonkeyShow

class MonkeyTamer: 
  """ This is a class to handle MonkeyShow, and displays the result in basic textual form.
  It prints out the names of tricks as they are run, errors as they
  occur, and a summary of the results at the end of the test run.
  """
  def play(self, show, reporter=None):
    if(reporter is None) : 
      reporter = ConsoleReporter()
    strat_time = time.time()
    show.play(reporter)        
    reporter.generateReport()
    stop_time = time.time()
    time_taken = stop_time - strat_time
    reporter.logger.info("Play %d test trick%s in %.3fs" % (show.count(), show.count() != 1 and "s" or "", time_taken))
    return reporter
# End MonkeyTamer

